package core;

import java.util.*;

import java.awt.image.*;
 
public class KMeans {

	// numero di clusters
	private int _k;
	// clusters
	private ClusterAbstraction[] _clusters;
	// numero di iterazioni
	private int _nIterations;
	// punti
	private Vector <Observation> _kMeansPoints;
	

	/**	
	 * Costruttore generico
	 */
	public KMeans()
	{
		super();
		_nIterations = 0;
	}

	/**	
	 * Costruttore 
	 * @param k numero di clusters
	 */
	public KMeans(int k) 
    {
    	this();
		_k = k;	
		_clusters = new ClusterAbstraction[_k];

	} 

	/**	
	 * Costruttore 
	 * @param k numero di clusters
	 * @param points punti
	 */
    public KMeans(int k, Vector <Observation> points) 
    {
    	this(k);
    	setData(points);
	} 

    /**
     * setData imposta i punti
     * @param points punti
     */
    public void setData(Vector <Observation> points) 
	{
    	_kMeansPoints = points;
	} 

    /**
     * runKMeans esegue il kmeans
     * @param img Immagine di input da cui prelevare gli intorni
     */
    public void runKMeans(BufferedImage img) 
	{
    	Neighborhood mean = null;
		// Seleziona k punti come medie iniziali
		for (int i=0; i < _k; i++)
		{
			mean = _kMeansPoints.elementAt((int)(Math.random()*(_kMeansPoints.size())))._point;
			_clusters[i] = new ClusterAbstraction(i, mean);
		}
		
		
		do 
		{
		// Formo i k clusters
			Iterator i = _kMeansPoints.iterator();
			while (i.hasNext())
				assignToCluster((Observation)i.next(), img);	
			_nIterations++;
		}
		// Ripeto fino a che le medie non cambiano
		while (updateMeans(img));
	
	} 

//  done
	private void assignToCluster(Observation o, BufferedImage img) 
	{
		double minDistance;
		int currentCluster = 0;
		double currentDistance;
		minDistance = Neighborhood.distance(_clusters[o._cluster].getMean(), o._point, img);
		//per tutti i clusters
		for (int index=0; index < _k; index++)
		{
			currentDistance = Neighborhood.distance(_clusters[index].getMean(), o._point, img);
			if (currentDistance < minDistance) 
			{
				minDistance = currentDistance;
				currentCluster = index;
			}
		}
		// imposto il nuovo cluster
		_clusters[currentCluster].addPoint(o);	
		
	} 
	
//  done	
	private boolean updateMeans(BufferedImage img) {
	
		boolean[] reply = new boolean[_k];
		boolean result = true;
		
		int[] size = new int[_k];
		Neighborhood[] pastMeans = new Neighborhood[_k];
		Neighborhood[] currentMeans = new Neighborhood[_k];
		
		for (int j=0; j<_k; j++) 
		{
			size[j] = getPoints(j).size();
			pastMeans[j] = _clusters[j].getMean();
			currentMeans[j] = calculateMean(j, img);
		}
		
		/*
		for (int j=0; j < _k; j++ ) 
			if ((size[j] != 0) && 
					(Neighborhood.distance(pastMeans[j], currentMeans[j], img) != 0.0 ))
					reply[j] = true;
			else reply[j] = false;
			
		for (int j=0; j < _k; j++ )
			result = result && reply[j];
		
		return result;
		*/
		
		if (_nIterations == 25)
			return true;
		else 
			return false;
	} 

	public int getK() 
	{
		return _k;
	} 


	public Neighborhood calculateMean(int index, BufferedImage img)
	{
		Vector<Neighborhood> points = getPoints(index);
		double[][] avg = null;
		
		Neighborhood n;
		Iterator iter = points.iterator();
		while (iter.hasNext()) 
		{
			n = (Neighborhood)iter.next();
			if (avg == null)
			{
				avg = new double[n.getWidth()][n.getWidth()];
				for (int i=0; i<n.getWidth(); i++)
		            for(int j=0; j<n.getWidth(); j++)
		            {
		                //Al momento lo calcolo soltanto su un canale
		                avg[i][j] = n.getLuminanceFromImage(img)[i][j]/points.size();
		            }
			}
			else
				for (int i=0; i<n.getWidth(); i++)
			            for(int j=0; j<n.getWidth(); j++)
			            {
			                //Al momento lo calcolo soltanto su un canale
			                avg[i][j] += n.getLuminanceFromImage(img)[i][j]/points.size();
			            }
			
		}
		
		iter = points.iterator();
		int[][] current;
		double minDistance = Double.MAX_VALUE;
		double currentDistance = Double.MAX_VALUE;
		Neighborhood result = null;
		while (iter.hasNext()) 
		{
			n = (Neighborhood)iter.next();
			current = n.getLuminanceFromImage(img);
			for (int i=0; i<n.getWidth(); i++)
	            for(int j=0; j<n.getWidth(); j++)
	            {
	                //Al momento lo calcolo soltanto su un canale
	            	currentDistance +=  (Math.pow((float)current[i][j]/256 - (float)avg[i][j]/256, 2));
	            }
			currentDistance = Math.sqrt(currentDistance);
			if (currentDistance < minDistance)
			{
				minDistance = currentDistance;
				result = n;
			}
		}
		
		return result;
	}
	

	public Vector <Neighborhood> getPoints(int n)
	{
		Vector <Neighborhood> result = new Vector <Neighborhood>();
		Observation ob;
		Iterator i = _kMeansPoints.iterator();
		while (i.hasNext())
		{
			ob = (Observation)i.next();
			if (ob._cluster == n)
				result.add(ob._point);
		}
		return result;
	}
	
	public Neighborhood getMean(int n)
	{
		return _clusters[n].getMean();
	}
	
	/*
	public static void main(String[] args) 
	{
            
	} 
	*/

}

class Observation {
	public Neighborhood _point;
	public int _cluster;
	
	public Observation(Neighborhood n, int p)
	{
		_point = n;
		_cluster = p; 
	}
}